#include "player.h"

namespace sct
{

static const char* gUrlRootPath = "/";
static const char* gLabelConf = "conf";
static const char* gLabelRtsp = "rtsp";
static const char* gLabelUrls = "urls";
static const char* gXMLVersion = "1.0";
static const char* gMethodGet = "GET";
static const char* gMethodPost = "POST";


xmlXPathObjectPtr
getnodeset (xmlDocPtr doc, xmlChar *xpath){
    xmlXPathContextPtr context;
    xmlXPathObjectPtr result;
    context = xmlXPathNewContext(doc);
    if (context == NULL) {
        printf("Error in xmlXPathNewContext\n");
        return NULL;
    }
    result = xmlXPathEvalExpression(xpath, context);
    xmlXPathFreeContext(context);
    if (result == NULL) {
        printf("Error in xmlXPathEvalExpression\n");
        return NULL;
    }
    if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
        xmlXPathFreeObject(result);
        printf("No result\n");
        return NULL;
    }
    return result;
}

CPlayer::CPlayer():
    mWin(NULL),
    mAudioOutput(NULL),
    mRtspClient(NULL),
    mRecvThd(NULL),
    mState(Initing),
    mDemux(NULL),
    mVideoDecode(NULL),
    mAudioDecode(NULL)
{
    mWin = new Window;
    mWin->show();

    mAudioOutput = new CAudioOutput(32000);

    CDecodeInterface::initlib();

    mVideoDecode = CDecodeInterface::create("decode", CT_H264);
    mVideoDecode->init();

    mAudioDecode = CDecodeInterface::create("decode", CT_AAC);
    mAudioDecode->init();

    mDemux = new CTsDemux;
    mDemux->addCallback(this);

    mVideoPlayPipeLine = new CPlayPipeLine;
    mVideoPlayPipeLine->setType(CPlayPipeLine::Video);
    mVideoPlayPipeLine->setRender(mWin->getScreen());
    mVideoPlayPipeLine->setDecoder(mVideoDecode);

    mVideoPlayPipeLine->init();

    mAudioPlayPipeLine = new CPlayPipeLine;
    mAudioPlayPipeLine->setType(CPlayPipeLine::Audio);
    mAudioPlayPipeLine->setRender(mAudioOutput);
    mAudioPlayPipeLine->setDecoder(mAudioDecode);

    mAudioPlayPipeLine->init();

    mHttpClient = CHttpClientInterface::create("httpclient");
    mHttpBuffMaxSz = 4096;
    mHttpBuff = new uint8_t[mHttpBuffMaxSz];


    int res = 0;

    ///try to get url
    xmlNodePtr root, node, sub;
    xmlDocPtr doc;
    xmlChar *xmlbuff;
    int buffersize;

    doc = xmlNewDoc(BAD_CAST gXMLVersion);
    root = xmlNewNode(NULL, BAD_CAST gLabelConf);
    xmlDocSetRootElement(doc, root);

    ///build body
    node = xmlNewNode(NULL, BAD_CAST "api");
    xmlAddChild(root, node);

    sub = xmlNewNode(NULL, BAD_CAST "mediacenter");
    xmlAddChild(node, sub);

    node = sub;
    sub = xmlNewNode(NULL, BAD_CAST "streamforwards");
    xmlAddChild(node, sub);

    node = sub;
    sub = xmlNewNode(NULL, BAD_CAST "streamforward");
    xmlNewProp(sub, BAD_CAST "machine", BAD_CAST "20160527a001");
    xmlNewProp(sub, BAD_CAST "streammajor", BAD_CAST "front");
    xmlAddChild(node, sub);

    xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);

    SCT_LOGD("snd:%d:%s", buffersize, xmlbuff);
    res = mHttpClient->post("http://192.168.1.67:8088/api/mediacenter/streamforwards",
                xmlbuff,
                buffersize,
                mHttpBuff,
                mHttpBuffMaxSz);
    SCT_LOGD("ret:%d:%s", res, mHttpBuff);

    xmlFree(xmlbuff);
    xmlFreeDoc(doc);


    ///try to get addr
    doc = xmlReadMemory((const char*)mHttpBuff, mHttpBuffMaxSz,
            "noname.xml", NULL, 0);

    ///get stream id
    int id = -1;

    xmlXPathObjectPtr result;
    xmlNodeSetPtr nodeset;
    int i;
    xmlChar *value;
    result = getnodeset (doc, (xmlChar*)"/conf/api/mediacenter/streamforwards/streamforward");
    if(result)
    {
        nodeset = result->nodesetval;
        for (i=0; i < nodeset->nodeNr; i++) {
            value = xmlNodeGetContent(nodeset->nodeTab[i]);

            id = atoi((char*)value);

            xmlFree(value);
        }

        xmlXPathFreeObject (result);
    }

    xmlFreeDoc(doc);

    mStreamId = id;

    char * strUrl = new char[1000];
    sprintf(strUrl, "http://192.168.1.67:8088/api/mediacenter/streamforwards/streamid/%d/url", id);
    res = mHttpClient->get(strUrl,
            mHttpBuff,
            mHttpBuffMaxSz);

    SCT_LOGD("ret:%d:%s", res, mHttpBuff);
    SCT_DELETE_ARRAY(strUrl);

    ///now parse ip, port
    doc = xmlReadMemory((const char*)mHttpBuff, mHttpBuffMaxSz,
            "noname.xml", NULL, 0);

    std::string url;
    result = getnodeset (doc, (xmlChar*)"/conf/api/mediacenter/streamforwards/stream/url");
    if(result)
    {
        nodeset = result->nodesetval;
        for (i=0; i < nodeset->nodeNr; i++) {
            value = xmlNodeGetContent(nodeset->nodeTab[i]);

            url = (char*)value;

            xmlFree(value);
        }

        xmlXPathFreeObject (result);
    }

    SCT_LOGD("target info:%s", url.c_str());
    xmlFreeDoc(doc);

    mRtspClient = CRTSPClientInterface::create("rtspclient");
    mRtspClient->addCallback(this);
    mRtspClient->setServerUrl(url.c_str());
//    mRtspClient->setServerUrl("rtsp://192.168.1.67:8554/live-0");
//    mRtspClient->setServerUrl("rtsp://192.168.1.67:8554/mpeg2TransportStreamTest.ts");

    mRecvThd = new CThread("rtsp_recv_thd");
    mRecvThd->addCallback(this);
    mRecvThd->start();


    CObject::initlib();
    mTimerBootup = addTimer(3000, CObject::TT_OneShot);
    mObjectLoop = new CThread("object_loop");
    mObjectLoop->addCallback(this);
    mObjectLoop->start();

}

CPlayer::~CPlayer()
{
    delTimer(mTimerPeriod);
    SCT_DELETE(mObjectLoop);

    SCT_DELETE(mHttpClient);
    SCT_DELETE_ARRAY(mHttpBuff);

    SCT_DELETE(mRecvThd);

    SCT_DELETE(mRtspClient);
    SCT_DELETE(mDemux);

    SCT_DELETE(mVideoPlayPipeLine);
    SCT_DELETE(mAudioPlayPipeLine);

    SCT_DELETE(mAudioDecode);
    SCT_DELETE(mVideoDecode);

    SCT_DELETE(mAudioOutput);
    SCT_DELETE(mWin);
}

void CPlayer::onTimerTimeout(const int id)
{
    SCT_LOGD("timer:%d", id);

    if(id == mTimerBootup)
    {
        mTimerPeriod = addTimer(1000, TT_Periodic);
    }
    else if(id == mTimerPeriod)
    {
        ///keepalive

        char * strUrl = new char[1000];

        sprintf(strUrl, "http://192.168.1.67:8088/api/mediacenter/streamforwards/streamid/%d/status", mStreamId);
        int res = mHttpClient->get(strUrl,
                mHttpBuff,
                mHttpBuffMaxSz);

        if(res > 0){mHttpBuff[res] = '\0';}
//        SCT_LOGD("ret:%d :%s", res, mHttpBuff);
        SCT_DELETE_ARRAY(strUrl);
    }
}

int CPlayer::main(CThread * caller)
{
    if(caller == mRecvThd)
    {
        mState = Running;
        while(Running == mState)
        {
            mRtspClient->demux();
        }
    }
    else if(caller == mObjectLoop)
    {
        while(1)
        {
            CObject::tick();
            usleep(10000);
        }
    }
    return 0;
}
void CPlayer::onRecvData(CRTSPClientInterface* caller, const void* d,
            const int len,
            const CRTSPClientInterface::CBlockInfo & info)
{
    if(mRtspClient == caller)
    {
        mDemux->demux(d);
    }
}

void CPlayer::onPcrUpdate(CTsDemux* caller, const CTsDemux::CPmt* pmt)
{
//    SCT_LOGD("P:pcr:%10d => %10d", pmt->pcr, pmt->pcr * 100/9);
    int64_t pcr = pmt->pcr * 100/9;
    pcr -= 1000000;///delay 6 sec

    mVideoPlayPipeLine->updateSyncClock(currentTime()*1000000, pcr);
    mAudioPlayPipeLine->updateSyncClock(currentTime()*1000000, pcr);

    mVideoPlayPipeLine->updateSyncValue(-mAudioOutput->delay2Render());
    mAudioPlayPipeLine->updateSyncValue(mWin->getScreen()->delay2Render());
}

void CPlayer::onRecvPesPacket(CTsDemux* caller, const CTsDemux::CPes * pes)
{
    if(mDemux == caller)
    {
        switch(pes->type)
        {
        case CTSMux::ST_H264:
        {
            mVideoPlayPipeLine->pushEsData(pes->buf, pes->len, pes->dts * 100/9, pes->pts * 100/9);
            break;
        }

        case CTSMux::ST_AAC:
        {
            mAudioPlayPipeLine->pushEsData(pes->buf, pes->len, pes->dts * 100/9, pes->pts * 100/9);
            //SCT_LOGD("get aac packet");
            break;
        }

        default:
        {
            SCT_LOGD();
            break;
        }
        }

    }
}

void CPlayPipeLine::setType(const Type type)
{
    mType = type;
}

void CPlayPipeLine::setDecoder(CDecodeInterface *decoder)
{
    mDecoder = decoder;
}

void CPlayPipeLine::setRender(CRenderInterface *render)
{
    mRender = render;
}

void CPlayPipeLine::init()
{
    mMutex = new CMutex;

    mDecoder->addCallback(this);

    mRenderThread = new CThread("RenderPipeLine");
    mRenderThread->addCallback(this);

    mDecodeThread = new CThread("DecodePipeLine");
    mDecodeThread->addCallback(this);

    mRenderThread->start();
    mDecodeThread->start();
}

int CPlayPipeLine::main(CThread * caller)
{
    if(caller == mDecodeThread)
    {
        mDecodeState = Running;
        decodeControl();
    }
    else if(caller == mRenderThread)
    {
        mRenderState = Running;
        renderControl();
    }


    return 0;
}

void CPlayPipeLine::renderControl()
{
    int fps = 0;
    double lastFpsDetectTime = currentTime();
    while(Running == mRenderState)
    {
        bool needSleep = true;

        int64_t clk = toSyncClock( currentTime()*1000000 );

        mMutex->lock();
        clk += mSyncValue;

        ///render clk delay for decode cost
        clk -= 80000;

        ///push video data to render
        int nRender = 0;
        while(!mRawDatas.empty())
        {
            CStreamData* item = mRawDatas.front();
            if(item->pts > clk)
            {
                break;
            }

            mRawDatas.pop_front();

            mMutex->unlock();

            needSleep = false;

            if(Video == mType)
            {
                nRender++;
                fps ++;
                mRender->updatePicture(item->data, item->width, item->height);
            }

            if(Audio == mType)
            {
                CRenderInterface::CDataInfo info;
                info.sample_rate = item->sample_rate;
                mRender->pushData(item->data, item->len, info);
            }

            CStreamData::free(item);

            mMutex->lock();
        }

        mMutex->unlock();

        if(needSleep)
        {
            usleep(10000);
        }

        if(Video == mType)
        {
            ///collect some info

            if(nRender > 1)
            {
                nRender--;
                SCT_LOGD("skip %d frame(s)", nRender);
            }

            double curTime = currentTime();
            if(curTime-lastFpsDetectTime>1)
            {
                SCT_LOGD("fps:%d", fps);
                fps = 0;

                lastFpsDetectTime = curTime;
            }
        }
    }
}

void CPlayPipeLine::decodeControl()
{
    while(Running == mDecodeState)
    {
        bool needSleep = true;
        int64_t clk = toSyncClock( currentTime()*1000000 );

        mMutex->lock();
        ///push video data to decode
        while(!mEsDatas.empty())
        {
            CStreamData* item = mEsDatas.front();

            ///adj clk, delay play
            int64_t diff = clk - item->dts;
            diff = diff > 0? diff:-diff;
            if(diff > 15000000)
            {
                SCT_LOGD("sync err:%lld %lld", mSyncStreamPcr, item->dts);

                mEsDatas.pop_front();
                CStreamData::free(item);
                break;
            }

            if(item->dts > clk)
            {
                break;
            }

            mEsDatas.pop_front();

            mMutex->unlock();

            needSleep = false;

            try
            {
                double t1 = currentTime();
                mDecoder->decode(item->data, item->len, item->pts);
                double t2 = currentTime();
                t2 -= t1;
                if(t2 > 0.03)
                {
                    SCT_LOGD("decode cost:%f", t2);
                }
            }
            catch(int x)
            {
                ///ignore
            }

            CStreamData::free(item);

            mMutex->lock();
        }

        mMutex->unlock();

        if(needSleep)
        {
            usleep(10000);
        }
    }
}

int64_t CPlayPipeLine::toSyncClock(const int64_t clk)
{
    CAutoLock locker(mMutex);
    return (clk - mSyncSystemClock) + mSyncStreamPcr;
}


void CPlayPipeLine::onGenData(CDecodeInterface* caller,
            const void* buf, const int len,
            const CDecodeInterface::CBlockInfo & info)
{
    if(mDecoder == caller)
    {
        ///insert by the pts
        CStreamData * item = new CStreamData;

        item->len = len;
        item->data = new uint8_t[item->len];
        memcpy(item->data, buf, item->len);

        item->pts = info.pts;

        if(Video == mType)
        {
            item->width = info.width;
            item->height = info.height;
        }

        if(Audio == mType)
        {
            item->sample_rate = info.sample_rate;
        }

        CAutoLock locker(mMutex);

        std::list<CStreamData*>::iterator it;
        for(it = mRawDatas.begin(); it != mRawDatas.end(); it++)
        {
            CStreamData* loc = *it;
            if(loc->pts > item->pts)
            {
                break;
            }
        }

        if(it != mRawDatas.end())
        {
            mRawDatas.insert(it, item);
        }
        else
        {
            mRawDatas.push_back(item);
        }
    }
}

void CPlayPipeLine::updateSyncClock(const int64_t systemClk, const int64_t streamPcr)
{
    CAutoLock locker(mMutex);

    if(!mSync)
    {
        mSync = true;
        mSyncSystemClock = systemClk;
        mSyncStreamPcr = streamPcr;

        SCT_LOGD("sync clock:%10lld => %10lld", systemClk, streamPcr);
    }
}

void CPlayPipeLine::pushEsData(const uint8_t * d, const int len, const int64_t dts, const int64_t pts)
{
    CAutoLock locker(mMutex);

    if(!mSync)
    {
        return;
    }

    CStreamData * item = new CStreamData;

    item->len = len;
    item->data = new uint8_t[item->len];
    memcpy(item->data, d, item->len);

    item->dts = dts;
    item->pts = pts;

    mEsDatas.push_back(item);
}

void CPlayPipeLine::updateSyncValue(const int64_t value)
{
    CAutoLock locker(mMutex);

    mSyncValue = value;
}

CPlayPipeLine::CPlayPipeLine():
    mType(Video),
    mDecodeState(Initing),
    mRenderState(Initing),
    mMutex(NULL),
    mDecodeThread(NULL),
    mRenderThread(NULL),
    mDecoder(NULL),
    mRender(NULL),
    mSync(false),
    mSyncSystemClock(0),
    mSyncStreamPcr(0),
    mSyncValue(0)
{
    ;
}

CPlayPipeLine::~CPlayPipeLine()
{
    mDecodeState = Terminated;
    SCT_DELETE(mDecodeThread);

    mRenderState = Terminated;
    SCT_DELETE(mRenderThread);

    SCT_DELETE(mMutex);

    while(!mEsDatas.empty())
    {
        CStreamData* item = mEsDatas.front();
        mEsDatas.pop_front();

        CStreamData::free(item);
    }

    while(!mRawDatas.empty())
    {
        CStreamData* item = mRawDatas.front();
        mRawDatas.pop_front();

        CStreamData::free(item);
    }
}
}
